﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DiscoveryLogic.Analytics.BusinessLogic.Measures;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Measures;
using DiscoveryLogic.Analytics.DataObjects.Networks;
using DiscoveryLogic.Common.Utilities;

namespace DiscoveryLogic.Analytics.BusinessLogic.Clusters
{
    public class NetworkFuzzyClusterWorker
    {
        private double _ExternalSparseFactor = 0.25;
        private double _InternalDenseFactor = 1.0;
        private Network _Network;

        public NetworkFuzzyClusterWorker(Network network)
        {
            this._Network = network;
            this._InternalDenseFactor = 0.75;
            this._ExternalSparseFactor = 0.25;
        }

        public NetworkFuzzyClusterWorker(Network network, double alpha, double beta)
        {
            this._Network = network;
            this._ExternalSparseFactor = alpha;
            this._InternalDenseFactor = beta;
        }

        /// <summary>
        /// there is no overlap between clusters
        /// </summary>
        /// <returns></returns>
        public Dictionary<int, List<EntityBase>> ExclusivelyClusterNetwork()
        {
            
            Dictionary<EntityType, Dictionary<string, EntityBase>> entities = this._Network.GetEntities();
            List<EntityBase> processedEntities=new List<EntityBase>();
            Dictionary<EntityBase, List<EntityBase>> seededClusters=new Dictionary<EntityBase, List<EntityBase>>();

            foreach (EntityBase entity in this._Network.Records)
            {
                if (processedEntities.Contains(entity))
                    continue;

                Type type = EntityTypeUtil.GetType(entity.EntityType);
                string key = entity.PKValue();
                List<EntityBase> candidates = new List<EntityBase>();
                candidates.Add(entity);
                foreach (Link link in this._Network.Links)
                {
                    if (link.FromType == type && link.FromKey == key)
                    {
                        EntityBase neighbor = entities[EntityTypeUtil.GetEntityType(link.ToType)][link.ToKey];
                        if (!candidates.Contains(neighbor))
                            candidates.Add(neighbor);
                    }
                    else if (link.ToType == type && link.ToKey == key)
                    {
                        EntityBase neighbor = entities[EntityTypeUtil.GetEntityType(link.FromType)][link.FromKey];
                        if (!candidates.Contains(neighbor))
                            candidates.Add(neighbor);
                    }
                }

                if (candidates.Count <= 1)
                {
                    processedEntities.Add(entity);
                    continue;
                }

                List<Link> linksWithinCandidates = Network.GetLinksWithinEntities(this._Network.Links, candidates);
                Dictionary<EntityBase, int> degrees = new Dictionary<EntityBase, int>();
                foreach (EntityBase candidate in candidates)
                {
                    int degree = 0;
                    foreach (Link link in linksWithinCandidates)
                    {
                        if (link.FromType == EntityTypeUtil.GetType(candidate.EntityType) &&
                            link.FromKey == candidate.PKValue())
                            degree++;
                        else if (link.ToType == EntityTypeUtil.GetType(candidate.EntityType) &&
                                 link.ToKey == candidate.PKValue())
                            degree++;
                    }
                    degrees.Add(candidate, degree);
                }
                foreach (EntityBase candidate in degrees.Keys)
                {
                    if (candidate == entity)
                        continue;
                    double saturation = (double) degrees[candidate]/(candidates.Count - 1);
                    if (saturation < this._InternalDenseFactor)
                        candidates.Remove(candidate);
                }

                if (candidates.Count <= 1)
                {
                    processedEntities.Add(entity);
                }
                else
                {
                    List<Link> linkedIns = Network.GetLinksWithinEntities(this._Network.Links, candidates);
                    List<Link> linkedOuts = Network.GetLinksConnectedToEntities(this._Network.Links, candidates);
                    List<EntityBase> entityLinkedOut = new List<EntityBase>();
                    foreach (Link linkIn in linkedIns)
                    {
                        if (linkedOuts.Contains(linkIn))
                            linkedOuts.Remove(linkIn);
                    }
                    foreach (Link link in linkedOuts)
                    {
                        foreach (EntityBase candidate in candidates)
                        {
                            if (entityLinkedOut.Contains(candidate))
                                continue;
                            if (candidate.EntityType == EntityTypeUtil.GetEntityType(link.FromType) &&
                                candidate.PKValue() == link.FromKey)
                            {
                                entityLinkedOut.Add(candidate);
                                break;
                            }
                            else if (candidate.EntityType == EntityTypeUtil.GetEntityType(link.ToType) &&
                                     candidate.PKValue() == link.ToKey)
                            {
                                entityLinkedOut.Add(candidate);
                                break;
                            }
                        }
                    }
                    double densityIn = (double) linkedIns.Count*2/(candidates.Count*(candidates.Count - 1));
                    double densityOut = (double) entityLinkedOut.Count/candidates.Count;
                    if (densityIn >= this._InternalDenseFactor && this._ExternalSparseFactor >= densityOut)
                    {
                        seededClusters.Add(entity, candidates);
                    }
                    else
                        processedEntities.Add(entity);
                }
            }
            Dictionary<int, List<EntityBase>> clusters = this.MergeSeededEntityGroups(seededClusters);

            return clusters;
        }


        #region merge
        private Dictionary<int, List<EntityBase>> MergeSeededEntityGroups(Dictionary<EntityBase,List<EntityBase>> seededEntities)
        {
            Dictionary<int, List<EntityBase>> clusters = new Dictionary<int, List<EntityBase>>();

            List<EntityBase> seeds = seededEntities.Keys.ToList();
            List<EntityBase> processedSeeds=new List<EntityBase>();
            for(int i=0;i<seeds.Count;i++)
            {
                if(processedSeeds.Contains(seeds[i]))
                    continue;
                processedSeeds.Add(seeds[i]);
                List<EntityBase> entities = seededEntities[seeds[i]];
                for(int k=i+1;k<seeds.Count;k++)
                {
                    if(processedSeeds.Contains(seeds[k]))
                        continue;
                    List<EntityBase> toEntities = seededEntities[seeds[k]];
                    if(this.Overlap(entities, toEntities))
                    {
                        entities = this.Union(entities, toEntities);
                        processedSeeds.Add(seeds[k]);
                    }
                }
                int groupID = clusters.Count + 1;
                clusters.Add(groupID, entities);
            }
            return clusters;
        }

        private bool Overlap(List<EntityBase> list1, List<EntityBase> list2)
        {
            foreach(EntityBase entity1 in list1)
            {
                if(list2.Contains(entity1))
                {
                    return true;
                }
            }
            return false;
        }

        private List<EntityBase> Union(List<EntityBase> list1, List<EntityBase> list2)
        {
            foreach(EntityBase entity2 in list2)
            {
                if(!list1.Contains(entity2))
                    list1.Add(entity2);
            }
            return list1;
        }
        #endregion
    }
}
